<?php

declare(strict_types=1);

namespace DreamCat\ObjectValid\Validor;

use DreamCat\ObjectValid\Base\ValidAnnoInterface;
use DreamCat\ObjectValid\Exception\CheckErrorException;
use DreamCat\ObjectValid\Utils\AnnotationUtils;
use DreamCat\ObjectValid\ValidationInterface;
use ReflectionClass;
use ReflectionParameter;
use ReflectionProperty;

/**
 * 对象数值校验器，基于注解来做的
 * @author vijay
 */
class Validator implements ValidationInterface
{
    /**
     * @inheritDoc
     */
    public function validObject(object $object): void
    {
        $ref = new ReflectionClass($object);
        $filter = ReflectionProperty::IS_PUBLIC
            | ReflectionProperty::IS_PROTECTED
            | ReflectionProperty::IS_PRIVATE;
        do {
            foreach ($ref->getProperties($filter) as $property) {
                $this->checkProperty($object, $property);
            }
            $ref = $ref->getParentClass();
        } while ($ref);
    }

    /**
     * @inheritDoc
     */
    public function validParam(ReflectionParameter $reflectionParameter, mixed $paramValue): void
    {
        $this->checkValue($paramValue, $reflectionParameter);
    }

    /**
     * 检查值是否符合注解要求
     * @param mixed $value
     * @param ReflectionParameter|ReflectionProperty $reflection
     * @return void
     */
    protected function checkValue(mixed $value, ReflectionParameter|ReflectionProperty $reflection): void
    {
        foreach (AnnotationUtils::getAttrObjs($reflection, ValidAnnoInterface::class) as $attrObj) {
            if ($attrObj->isValid($value)) {
                continue;
            }
            $msg = $attrObj->errorMessage($reflection, $value);
            if (strlen($msg)) {
                throw new CheckErrorException($reflection, $value, $attrObj, $msg);
            }
        }
        $this->recursionCheck($value);
    }

    /**
     * 检查属性的值
     * @param object $object 对象
     * @param ReflectionProperty $reflection 要检查的属性反射
     * @return void
     */
    protected function checkProperty(object $object, ReflectionProperty $reflection): void
    {
        $value = $this->getPropertyValue($object, $reflection);
        $this->checkValue($value, $reflection);
    }

    /**
     * 获取属性的值
     * @param object $object 对象
     * @param ReflectionProperty $property 属性反射
     * @return mixed 属性值
     */
    protected function getPropertyValue(object $object, ReflectionProperty $property): mixed
    {
        $allow = $property->isPublic();
        if (!$allow) {
            $property->setAccessible(true);
        }
        if ($property->isInitialized($object)) {
            $value = $property->getValue($object);
        } else {
            throw new CheckErrorException($property, null, message: "{$property->getName()} 属性必填未填写");
        }
        if (!$allow) {
            $property->setAccessible(false);
        }
        return $value;
    }

    /**
     * 递归检查
     * @param mixed $value 要检查的值
     * @return void
     */
    protected function recursionCheck(mixed $value): void
    {
        if (is_array($value)) {
            foreach ($value as $item) {
                $this->recursionCheck($item);
            }
        } elseif (is_object($value)) {
            $this->validObject($value);
        }
    }
}

# end of file
